/*
 * Bunisoft the Open Source Communications Company
 * Copyright 2006, Bunisoft Inc.,
 *
 * Portions of this software are Copyright 2006, JBoss Inc., and 
 * individual contributors as indicated by the @authors tag.
 * See the copyright.txt in the distribution for a full listing of
 * individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.teremail.store.paged;



import static java.lang.System.arraycopy;

import java.io.InputStream;
import java.io.OutputStream;

import org.teremail.common.Log;
import org.teremail.store.Store;
import org.teremail.store.StoreException;

import com.google.inject.Inject;

/**
 * @author Michael Barker <mailto:mike@middlesoft.co.uk>
 * @version $Revision: 1.4 $
 */
public class PagedStore implements Store {
    final static Log log = Log.getLog(PagedStore.class);
    
    private final BlobFactory dao;

    @Inject
    public PagedStore(BlobFactory dao) {
        this.dao = dao;
    }
    
    public String createStoreItem() throws StoreException {
        Blob b = dao.createBlob();
        return b.getId();
    }

    public InputStream getInputStream(String id) {
        Blob b = dao.getBlob(id);
        return new PagedInputStream(this, id, b.getLength(), b.getPageSize());
    }

    public OutputStream getOutputStream(String id) {
        Blob b = dao.getBlob(id);
        return new PagedOutputStream(this, id, b.getPageSize());
    }

    /**
     * Reads <code>len</code> bytes from the paged store from <code>position</position>
     * within the store.
     * 
     */
    public int read(String id, long position, byte[] b, int off, int len)
            throws StoreException {
        
        log.debug("Read, Id: %s, Position: %d, Offset: %d, Length: %d", id, 
                position, off, len);

        Blob blob = dao.getBlob(id);

        int pageSize = blob.getPageSize();
        int firstPage = (int) (position / pageSize);
        int pageOffset = (int) (position % pageSize);
        int numPages = (int) Math.ceil((double) (pageOffset + len)
                / (double) pageSize);
        int totalBytes = 0;

        if (position < blob.getLength()) {
            int inputOffset = pageOffset;
            int outputOffset = off;

            for (int i = firstPage; i < (firstPage + numPages)
                    && i < blob.getNumPages(); i++) {
                byte[] page = blob.getPage(i);
                int outputLength = Math.min(page.length - inputOffset,
                        len - totalBytes);
                arraycopy(page, inputOffset, b, outputOffset, outputLength);
                inputOffset = 0;
                outputOffset += outputLength;
                totalBytes += outputLength;
            }
        } else {
            totalBytes = 0;
        }

        return totalBytes;
    }


    /**
     * Write the data in <code>b</code> to the db as a page or series of
     * pages.
     * 
     * @see org.teremail.meldware.mail.store.Store#write(Long, long, byte[], int, int)
     * 
     */
    public int write(String id, byte[] b, int off, int len)
            throws StoreException {
        log.debug("Write, Id: %s, Offset: %d, Length: %d", id, off, len);
        Blob blob = dao.getBlob(id);

        // First we need a little math.
        int pageSize = blob.getPageSize();
        long currentSize = blob.getLength();
        
        if (off == 0 && len == pageSize && currentSize % pageSize == 0) {
            blob.addPage(b);
            return len;
        } else {
            int offset = off;
            
            // Even up to the edge of the boundary.
            if (currentSize % pageSize != 0) {
                byte[] lastPage = blob.getLastPage();
                int toCopy = Math.min(pageSize - lastPage.length, len);
                byte[] newData = new byte[lastPage.length + toCopy];
                arraycopy(lastPage, 0, newData, 0, lastPage.length);
                arraycopy(b, 0, newData, lastPage.length, toCopy);
                
                offset += toCopy;
                blob.replaceLastPage(newData);
            } 
            
            while (offset < off + len) {
                int remaining = len - (offset - off);
                int toWrite = Math.min(pageSize, remaining);
                byte[] newData = new byte[toWrite];
                arraycopy(b, offset, newData, 0, toWrite);
                blob.addPage(newData);
                offset += toWrite;
            }
            return offset - off;
        }
    }

}
